#
# JobQueue.py
# (c) 2008 Will Baker.
#

from threading import Condition

from JobThread import JobThread
from Job import Job

class JobQueue:
    """
    A thread-safe class for managing a queue of jobs to be run in thread. A JobQueue
    has a certain number of threads in a pool which can be assigned to Jobs as they
    are added to the queue.
    """
    def __init__( self, name, pool_size ):
        self.name = name
        self.lock = Condition()
        self.jobs = []
        self.threads = []
        self.done = False
        
        for i in range(0, pool_size):
            thread = JobThread( self.name + ":" + str(i), self )
            self.threads.append( thread )
            
    def start( self ):
        """
        Start each of the threads in the pool.
        """
        for t in self.threads:
            t.start()
        
    def stop( self ):
        """
        Stops the JobQueue from running - any JobThread that is waiting for the next
        Job (see JobQueue.next_job) will be woken up at this point and will be given
        a None return value from JobQueue.next_job. 
        """
        self.lock.acquire()
        self.done = True
        self.lock.notifyAll()
        self.lock.release()
        
    def clear( self ):
        """
        Clears the pending jobs in the queues.
        """
        self.lock.acquire()
        self.jobs = []
        self.lock.release()
        
    def add_job( self, job ):
        """
        Add a new job to the queue. This is typically called from the main thread.
        """
        self.lock.acquire()
        
        if job.priority == Job.HighPriority:
            self.jobs.insert( 0, job )
        else:
            self.jobs.append( job )
            
        self.lock.notify()
        self.lock.release()
        
    def next_job( self ):
        """
        Request next job in the queue. This is called from the JobThreads when they
        have finished their current job.
        
        This method will block if there are no jobs and the JobQueue is still running.
        If the JobQueue has stopped then this function will return None, otherwise it 
        will return the next Job.
        
        A thread that recieves None from this method should exit as this indicates that
        the JobQueue has stopped running.
        """
        self.lock.acquire()
        
        job = None
        
        # This will cause the JobThread to block until either the JobQueue is flagged to exit or
        # another Job is added to the queue. 
        while not self.done and len(self.jobs) == 0:
            self.lock.wait()
        
        # If the JobQueue wasn't finished then the JobThread must have been woken up because
        # another job was added to the queue. Get the top Job and return it
        if not self.done:
            job = self.jobs.pop( 0 )
    
        self.lock.release()
        
        return job
